package org.filecollector.filecontroller.virtualcatalogue.virtualfile;

import java.io.*;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.OptionalLong;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * Abstract class which encapsulates business logic of this package
 */
abstract class VirtualFileStaticMethods {

    /**
     * Catch all throwing objects
     * @param checkingFunc any function
     * @return result of {@code checkingFunc}
     * @param <T> any returning type
     */
    public static <T> T throwableChecker(Supplier<T> checkingFunc) {
        try {
            return checkingFunc.get();
        } catch(InvalidFileException e) {
            throw e;
        } catch(SecurityException e) {
            throw new InvalidFileException(
                    String.format("Uncatched security troubles: %s", e.getMessage()),
                    FileTroubles.UNKNOWN_TROUBLES
            );
        } catch(Exception t) {
            throw new InvalidFileException(
                    String.format("Untracking troubles %s", t.getMessage()),
                    FileTroubles.UNKNOWN_TROUBLES
            );
        }
    }

    /**
     * Method constructs default Message for logger
     * @param comment - logger comment which explains situation
     * @param filePath - path to inner file
     * @return compiled message
     */
    public static String getLoggerMessage(String comment, String filePath) {
        return String.format("File %s %s", filePath, comment);
    }

    /**
     * Create and send message
     * @param filePath path to inner file
     * @param consumer consumer which get Message
     * @param checkVar variable which defines message
     * @param ifTrue comment if {@code checkVar} is true
     * @param ifFalse comment if {@code checkVar} is false
     */
    public static void debugMessage(String filePath, Consumer<String> consumer,
                                    boolean checkVar, String ifTrue, String ifFalse) {
        consumer.accept(getLoggerMessage(checkVar? ifTrue:ifFalse, filePath));
    }

    /**
     * Check existing of inner file
     * @param innerFile is checking file
     * @param warner consumer that get message if file is null or not exist
     * @return true if file exists and not null; false in other cases
     */
    public static boolean exists(File innerFile, Consumer<String> warner) {
        if (innerFile == null) {
            warner.accept("Caution! File is null");
            return false;
        }
        if (!innerFile.exists()) {
            warner.accept(String.format("Caution! File %s doesn't exist", innerFile.getPath()));
            return false;
        }
        return true;
    }

    /**
     * Method calculates checksum
     * @param util calculates checksum
     * @param file inner file
     * @param warner consumer that get message when something goes wrong
     * @return checksum of {@code file} as Long
     */
    public static Long getCheckSum(Function<InputStream, OptionalLong> util, File file, Consumer<String> warner) {
        long ret = 0L;
        try (InputStream is = Files.newInputStream(file.toPath())) {
            var optRet = util.apply(is);
            if (optRet.isEmpty()) {
                warner.accept(String.format("Checksum cannot be evaluated in file %s", file.getPath()));
                optRet = OptionalLong.of(0L);
            }
            ret = optRet.getAsLong();
        } catch (IOException e) {
            warner.accept(
                    String.format(
                            "InputStream problem occurs in file %s; Message: %s",
                            file.getPath(),
                            e.getMessage()
                    )
            );
        }
        return ret;
    }

    /**
     * Directly writes to file
     * @param buffer {@code char[]} buffer that should be written to file
     * @param file inner file
     * @param append variable that defines if data will be added to the file
     * @param warner consumer that get message if something goes wrong
     * @param debugger consumer that get message if everything goes correct
     * @return true if writing correct; false in others cases
     */
    public static boolean writeDirectly(char[] buffer, File file, boolean append,
                                        Consumer<String> warner, Consumer<String> debugger) {
        debugger.accept("is writing directly");
        if (!file.canWrite()) {
            warner.accept(String.format("File %s cannot be written due to permissions", file.getPath()));
            throw new InvalidFileException(
                    String.format("File %s cannot be written due to permissions", file.getPath()),
                    FileTroubles.UNWRITABLE,
                    file.toPath()
            );
        }
        try(FileWriter writer = new FileWriter(file, append)) {
            writer.write(buffer);
        } catch (IOException | RuntimeException e) {
            warner.accept(String.format("writing doesn't complete due to %s. Data throw", e));
            return false;
        }
        debugger.accept("has been wrote");
        return true;
    }

    /**
     * Touching file
     * @param filePath target file
     * @param informer consumer which get message if everything is ok
     * @param warner consumer which get message if something goes ok
     * @return true if everything goes correct; false in other cases
     */
    public static boolean touch(String filePath, Consumer<String> informer, Consumer<String> warner) {
        File file = new File(filePath);
        if (file.exists()) {
            informer.accept(String.format("File %s already exists", file.getPath()));
            return true;
        }
        try {
            var ret = file.createNewFile();
            if (!ret) {
                warner.accept(String.format("File %s cannot be created", file.getPath()));
                return false;
            }
            if (!file.setWritable(true)) {
                warner.accept(String.format("File %s cannot be set write", file.getPath()));
                return false;
            }
            if (!file.setReadable(true)) {
                warner.accept(String.format("File %s cannot be set read", file.getPath()));
                return false;
            }
            if (!file.setLastModified(System.currentTimeMillis())) {
                warner.accept(String.format("File %s cannot be set current time", file.getPath()));
            }
            informer.accept(String.format("File %s created", file.getPath()));
            return true;
        } catch (IOException e) {
            warner.accept(String.format("File %s cannot be touched due to %s", file.getPath(), e));
            return false;
        }
    }

    /**
     * File directly reads buffer of {@code size} or less
     * @param size maximum size of buffer
     * @param file target file
     * @param warner consumer which get message if something is not ok
     * @param debugger consumer which get message if everything is ok
     * @return {@code char[]} of file's size or {@code size}
     */
    public static char[] readDirectly(final int size, final File file,
                                      final Consumer<String> warner, final Consumer<String> debugger) {
        debugger.accept("append directly");
        var ret = new char[size];
        int readSize;
        if (!file.canRead()) {
            warner.accept(String.format("File %s cannot be read due to permissions", file.getPath()));
            throw new InvalidFileException(
                    String.format("File %s cannot be read due to permissions", file.getPath()),
                    FileTroubles.UNREADABLE,
                    file.toPath()
            );
        }
        try(FileReader reader = new FileReader(file)) {
            readSize = reader.read(ret);
        } catch (IOException | RuntimeException e) {
            warner.accept(String.format("reading doesn't complete due to %s. Data throw", e));
            return new char[0];
        }
        if (readSize != size) {
            return Arrays.copyOf(ret, readSize);
        }
        debugger.accept("has been append");
        return ret;
    }
}
